#
# Process an email message text
#
package Mach;

use strict;

use HTTP::Date;
use DBI;
use CPUparse;
use Counter; # for the match-to-registered and for the DB name stuff
use MachMess;

use vars qw($dbh $askfor $store $update $setfkeystm $counter $machines);
use vars qw($DEBUG %mapnotes $wrap32);

use vars qw($exactcount $changecount);

$DEBUG = 0;

# constant
$wrap32 = 497.1026962962; # 2^32/(100*24*60*60) - the jiffies wrap

initdbconn();

sub initdbconn {


    $counter = Counter::open(); # read-only mode
    $machines = Counter::openfile("machines");

    $dbh = FileTable::dbhandle();
# Prepare some interesting queries

    $askfor = $dbh->prepare("SELECT uniqueid, max_uptime, uptime, UNIX_TIMESTAMP(updated)  FROM automach WHERE uniqueid = ?");
    $store = $dbh->prepare("INSERT INTO automach
          (name, uniqueid, updated, lastmessage, rectext,
           uptime, cpu, bogomips, mailfrom, method, kernel, max_uptime, megahz)
    VALUES(?, ?,        ?,    ?,       ?,       ?,      ?,   ?, ?, ?, ?, ?, ?)");
    $update = $dbh->prepare("UPDATE automach
       SET name=?, updated=?, lastmessage=?, rectext=?,
            uptime=?, cpu=?, bogomips=?, mailfrom=?, method=?, kernel=?,
            max_uptime=?, megahz=?
       WHERE uniqueid = ?");
    $setfkeystm = $dbh->prepare("UPDATE automach
                             SET f_key = ?, f_owner = ?
                             WHERE uniqueid = ?");

}



sub processmessage {

    my $message = shift;
    my %entry;
    my $msgdate;
    my $mailfrom;
    my $entrytext;
    my $setfkey = 0;

    if ($message) {
#	print "Storing record\n";
    } else {
	return;
    }
    $DEBUG && warn "\nStarting message\n";
    # just to get the start of the OO....
    my $mess = MachMess->new($message);
    warn "Version is ", $mess->version(), "\n";
    $message = $mess->rawdata();
    $mess->filenames(); # just generate the warn()
    # ----------- Parse message ------------------
    if ($mess->basepart()) {
	my ($rec, @lines, $line);
	$entrytext = $mess->basepart();
	@lines = split(/\n/, $entrytext);
	for $line (@lines) {
	    if ($line =~ /^(\S+):\s*(.*)$/) {
		$entry{$1} = $2;
	    } else {
		warn "Parse error: $line\n";
	    }
	}
    } else {
	die "No MACHINE record found in message\n";
    }
    if ($message =~ /\nFrom:(.*)\n/) {
	$DEBUG && warn "Message from $1\n";
    }
    if ($message =~ /\nDate:(.*)\n/) {
	my $datestr = $1;
	$datestr =~ s/\(.*\)//g;
	my $rectime = HTTP::Date::str2time($datestr);
	die "Unparseable date: $datestr\n" if !$rectime;
	$msgdate = HTTP::Date::time2iso($rectime);
    } else {
	warn "No date in message\n";
	$msgdate = HTTP::Date::time2iso(time());
    }
    if ($message =~ /\nFrom:\s*(.*)\n/) {
	$mailfrom = $1;
    } else {
	warn "No From field in message\n";
	$mailfrom = "testprogram?";
    }
    # --------- Process some fields -----------
    my $olduptime = parseuptime($entry{uptime});
    $entry{uptime} = $mess->uptime();
    if (int($olduptime) ne int($entry{uptime})) {
	warn "Change in uptime: $olduptime to $entry{uptime}\n";
    }
    my $cpuold = CPUparse::parsecpu(\%entry);
    my $cpu = $mess->cpu();
    if ($cpuold ne $cpu && $cpuold !~ /No CPU info/) {
	warn "CPU parse changed: old $cpuold new $cpu\n";
    }
    if ($cpu =~ /^(.*)\/(\d+)/) {
	$entry{cpu} = $1;
	$entry{megahz} = $2;
    } else {
	warn "CPU has strange form: $cpu\n";
    }
    $DEBUG && warn "CPU is $entry{cpu}\n";
    $entry{"disk"} = $mess->disk(); # not stored yet, though...
    $DEBUG && warn "Found ", $entry{"disk"}, " Mbytes of disk\n";
    # See if owner exists...
    if ($entry{owner} && $entry{key}) {
	$entry{owner} =~ s/-\d+$//; # some give key, not user number
	my $user = $counter->get($entry{owner});
	if (!$user) {
	    $DEBUG && warn "Match: User $entry{owner} not found\n";
	} else {
	    $DEBUG && warn "Userrec $entry{owner}\n";
	    my $mach = $machines->get($entry{key});
	    if (!$mach) {
		$DEBUG && warn "Match: Machine $entry{key} not found\n";
	    } else {
		$DEBUG && warn "Match: Machine $entry{key} FOUND\n";
		$setfkey = 1;
	    }
	}
    } else {
	$DEBUG && warn "Unkeyed entry $entry{uniqueid}\n";
    }

    # --------- Put message in database ----------
    if (!$entry{uniqueid}) {
	warn "No uniqueid - can't store\n",
	return;
    }
    # See if it exists...

    $DEBUG && warn "Fetching uniqueid $entry{uniqueid} from DB\n";
    my $exists = $askfor->execute($entry{uniqueid}) || die "Select check failed\n";
    if ($exists == 0) {
	$DEBUG && warn "New record for $entry{name}\n";
	$DEBUG && warn "Bogomips is $entry{bogomips}\n";
	$store->execute($entry{name}, $entry{uniqueid}, $msgdate,
                        $message, $entrytext,
			$entry{uptime}, $entry{cpu}, $entry{bogomips}+0,
			$mailfrom, $entry{method}, $entry{kernel},
			$entry{uptime}, $entry{megahz})
	    || die "Insert statement failed\n";
    } else {
	my @tmp = $askfor->fetchrow_array;
	my $max_uptime = $tmp[1];
	# The uptime-loop detection....
	my $prevtime = $tmp[3]; # MySQL converted this to seconds-since-epoch
	$DEBUG && warn "Prev uptime: $prevtime\n";
	my $prevuptime = $tmp[2];
	my $nowtime =  HTTP::Date::str2time($msgdate);
	$DEBUG && warn "Nowtime: $nowtime\n";
	my $deltadays = ($nowtime - $prevtime) / 86400;
	my $expect_uptime = $prevuptime + $deltadays;
	my $diff = $expect_uptime - $entry{uptime};
	if ($prevtime > $nowtime) {
	    # ignore - this happens during log reproc only
	    warn "Uptime check: New date is older than stored - no wrap check\n";
	} elsif ($deltadays > 100) {
	    warn "Uptime check: Too large date difference $deltadays\n";
	} elsif (abs($diff) > 0.01) {
	    # something happened to the uptime that does not fit with the clock
	    if ($diff > ($wrap32 - 0.1)) {
		# difference MAY be due to wrap
		my $wrapcount = 0;
		while ($diff > ($wrap32 - 0.1)) {
		    $diff -= $wrap32;
		    ++ $wrapcount;
		}
		if ($diff > 0.1) {
		    warn "Uptime check: POSSIBLE WRAP $wrapcount - delta $diff\n";
		} else {
		    warn "Uptime check: ACCEPTED WRAP $wrapcount - delta $diff\n";
		    $entry{uptime} += $wrap32 * $wrapcount;
		}
	    } elsif ($entry{uptime} < $deltadays) {
		warn "Uptime check: Reboot - uptime $entry{uptime}, delta $deltadays\n";
	    } elsif (abs($diff) > 0.4 && abs($diff) < 0.41) {
		warn "Uptime check: DST jump :-( - ", $diff*24, "hours\n";
	    } else {
		$DEBUG && warn "Uptime check: NOT WRAP - delta $diff days\n";
	    }
	    ++ $changecount;
	} else {
	    $DEBUG && warn "Uptime: As expected ($exactcount / $changecount)\n";
	    ++ $exactcount;
	}
	#
	if ($entry{uptime} > $max_uptime + 0.01) {
	    # must have 0.01 because storing sometimes rounds down
	    $DEBUG && warn "Increasing uptime from $max_uptime to $entry{uptime}\n";
	    $max_uptime = $entry{uptime};
	}
	$DEBUG && warn "Update record for $entry{name}\n";
	$DEBUG && warn "Uptime is $entry{uptime}\n";
	$update->execute($entry{name}, $msgdate, $message, $entrytext,
			$entry{uptime}, $entry{cpu}, $entry{bogomips} + 0,
			  $mailfrom, $entry{method}, $entry{kernel},
			 $max_uptime, $entry{megahz},
			  $entry{uniqueid})
	    || die "Update statement failed\n";
    }
    if ($setfkey) {
	$setfkeystm->execute($entry{key}, $entry{owner}, $entry{uniqueid})
	    || die "Unable to update f_key and f_owner\n";
    }

    undef %entry;
    undef $entrytext;
    undef $message;
    undef $msgdate;
}

# Take an uptime string, return uptime in days

sub parseuptime {
    my $time = shift;
    my $timeout;
 
    if ($time =~ /^(\d+) days?, +(\d+):(\d+)$/) {
        $timeout = $1 + $2/24 + $3/(24*60);
    } elsif ($time =~ /^(\d+) min$/) {
        $timeout = $1 / (24*60);
    } elsif ($time =~ /^(\d+):(\d+)$/) {
        $timeout = $1 / 24 + $2 / (24*60);
    } elsif ($time =~ /^(\d+) days?, (\d+) min/) {
        $timeout = $1 + $2/(24*60);
    } elsif ($time =~ /^(\d+) days/) {
        $timeout = $1; # incredibly, this happened...
    } elsif ($time eq "") {
        # not given
        $timeout = 0;
    } else {
        warn "Unparseable uptime: \"$time\"\n";
        $timeout = 0;
    }
    $timeout;
}
